﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Routing;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Navigation.Services;
using Orchard.Core.Navigation.ViewModels;
using Orchard.Core.Title.Models;
using Orchard.Localization;
using Orchard.UI.Navigation;
using Orchard.Utility.Extensions;

using Orchard.Caching;
using Orchard.Environment.Extensions;
using Orchard;
using Orchard.Security.Permissions;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Orchard.Data;
using Orchard.Roles.Models;

namespace Rework.NavigationCache.Drivers {
    [OrchardSuppressDependency("Orchard.Core.Navigation.Drivers.MenuWidgetPartDriver")]
    public class MenuWidgetPartDriver : ContentPartDriver<MenuWidgetPart> {
        private readonly IContentManager _contentManager;
        private readonly INavigationManager _navigationManager;
        private readonly IWorkContextAccessor _workContextAccessor;
        private readonly IMenuService _menuService;

        private readonly ICacheManager _cacheManager;
        private readonly ISignals _signals;

        private readonly IOrchardServices _orchardServices;
        private readonly IRepository<UserRolesPartRecord> _userRolesRepository;

        public MenuWidgetPartDriver(
            IContentManager contentManager,
            INavigationManager navigationManager,
            IWorkContextAccessor workContextAccessor,
            IMenuService menuService,
            
            ICacheManager cacheManager,
            ISignals signals,
            
            IOrchardServices orchardServices,
            IRepository<UserRolesPartRecord> userRolesRepository) {
            _contentManager = contentManager;
            _navigationManager = navigationManager;
            _workContextAccessor = workContextAccessor;
            _menuService = menuService;

            _cacheManager = cacheManager;
            _signals = signals;

            _orchardServices = orchardServices;
            _userRolesRepository = userRolesRepository;
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }

        protected override string Prefix {
            get {
                return "MenuWidget";
            }
        }

        protected override DriverResult Display(MenuWidgetPart part, string displayType, dynamic shapeHelper) {
            return ContentShape( "Parts_MenuWidget", () => {
                var menu = _menuService.GetMenu(part.MenuContentItemId);

                if (menu == null) {
                    return null;
                }
                
                var menuName = menu.As<TitlePart>().Title.HtmlClassify();
                var currentCulture = _workContextAccessor.GetContext().CurrentCulture;
                // EMS CHANGE - start
                // Trying to get routevalues from the href
                string cacheKey = string.Format("MenuWidgetPartDriverCacheKey_{0}", part.MenuContentItemId);
                var user = _workContextAccessor.GetContext().CurrentUser;
                if (user != null) {
                    // Adding in a dependency on Orchard.Roles as it will be much more efficient to cache the
                    //  navigation against the user roles than each logged in user (each logged in user wouldn't scale well)
                    cacheKey = String.Format("{0}_{1}", cacheKey, "authenticated");
                    var currentUserRoleRecords = _userRolesRepository.Fetch(x => x.UserId == user.Id);
                    foreach (var userRoleRecord in currentUserRoleRecords.OrderBy(urr => urr.Role.Name)) {
                        // If the user has no 
                        cacheKey = String.Format("{0}_{1}", cacheKey, userRoleRecord.Role.Id);
                    }
                }
                else {
                    cacheKey = String.Format("{0}_{1}", cacheKey, "anonymous");
                }
                
                var menuItems = GetCachedMenuItems(menu, cacheKey);
                menuItems = TryGetRouteValues(menuItems);
                // EMS CHANGE - end

                var localized = new List<MenuItem>();
                foreach (var menuItem in menuItems) {
                    // if there is no associated content, it as culture neutral
                    if (menuItem.Content == null) {
                        localized.Add(menuItem);
                    }

                    // if the menu item is culture neutral or of the current culture
                    else if (String.IsNullOrEmpty(menuItem.Culture) || String.Equals(menuItem.Culture, currentCulture, StringComparison.OrdinalIgnoreCase)) {
                        localized.Add(menuItem);
                    }
                }

                menuItems = localized;

                var request = _workContextAccessor.GetContext().HttpContext.Request;
                var routeData = request.RequestContext.RouteData;
                var selectedPath = NavigationHelper.SetSelectedPath(menuItems, request, routeData);
                var menuShape = shapeHelper.Menu();

                if (part.Breadcrumb) {
                    menuItems = selectedPath ?? new Stack<MenuItem>();
                    foreach (var menuItem in menuItems) {
                        menuItem.Items = Enumerable.Empty<MenuItem>();
                    }

                    // apply level limits to breadcrumb
                    menuItems = menuItems.Skip(part.StartLevel - 1);
                    if (part.Levels > 0) {
                        menuItems = menuItems.Take(part.Levels);
                    }

                    var result = new List<MenuItem>(menuItems);

                    // inject the home page
                    if (part.AddHomePage) {
                        result.Insert(0, new MenuItem {
                            Href = _navigationManager.GetUrl("~/", null),
                            Text = T("Home")
                        });
                    }

                    // inject the current page
                    if (!part.AddCurrentPage) {
                        result.RemoveAt(result.Count - 1);
                    }

                    // prevent the home page to be added as the home page and the current page
                    if (result.Count == 2 && String.Equals(result[0].Href, result[1].Href, StringComparison.OrdinalIgnoreCase)) {
                        result.RemoveAt(1);
                    }

                    menuItems = result;
                    menuShape = shapeHelper.Breadcrumb();
                }
                else {
                    var topLevelItems = menuItems.ToList();

                    // apply start level by pushing children as top level items. When the start level is
                    // greater than 1 (ie. below the top level), only menu items along the selected path
                    // will be displayed.
                    for (var i = 0; topLevelItems.Any() && i < part.StartLevel - 1; i++) {
                        var temp = new List<MenuItem>();
                        // should the menu be filtered on the currently displayed page ?
                        if (part.ShowFullMenu) {
                            foreach (var menuItem in topLevelItems) {
                                temp.AddRange(menuItem.Items);
                            }
                        }
                        else if (selectedPath != null) {
                            topLevelItems = topLevelItems.Intersect(selectedPath.Where(x => x.Selected)).ToList();
                            foreach (var menuItem in topLevelItems) {
                                temp.AddRange(menuItem.Items);
                            }
                        }
                        topLevelItems = temp;
                    }

                    // limit the number of levels to display (down from and including the start level)
                    if (part.Levels > 0) {
                        var current = topLevelItems.ToList();
                        for (var i = 1; current.Any() && i < part.Levels; i++) {
                            var temp = new List<MenuItem>();
                            foreach (var menuItem in current) {
                                temp.AddRange(menuItem.Items);
                            }
                            current = temp;
                        }
                        // cut the sub-levels beneath any menu items that are at the lowest level being displayed
                        foreach (var menuItem in current) {
                            menuItem.Items = Enumerable.Empty<MenuItem>();
                        }
                    }
                    menuItems = topLevelItems;
                }

                menuShape.MenuName(menuName);
                menuShape.ContentItem(menu);

                NavigationHelper.PopulateMenu(shapeHelper, menuShape, menuShape, menuItems);

                return shapeHelper.Parts_MenuWidget(Menu: menuShape);
            });
        }

        protected override DriverResult Editor(MenuWidgetPart part, dynamic shapeHelper) {
            return ContentShape("Parts_MenuWidget_Edit", () => {

                var model = new MenuWidgetViewModel {
                    CurrentMenuId = part.MenuContentItemId,
                    StartLevel = part.StartLevel,
                    StopLevel = part.Levels,
                    Breadcrumb = part.Breadcrumb,
                    AddCurrentPage = part.AddCurrentPage,
                    AddHomePage = part.AddHomePage,
                    ShowFullMenu = part.ShowFullMenu,
                    Menus = _menuService.GetMenus(),
                };

                return shapeHelper.EditorTemplate(TemplateName: "Parts.MenuWidget.Edit", Model: model, Prefix: Prefix);
            });
        }

        protected override DriverResult Editor(MenuWidgetPart part, IUpdateModel updater, dynamic shapeHelper) {
            var model = new MenuWidgetViewModel();

            if (updater.TryUpdateModel(model, Prefix, null, null)) {
                part.StartLevel = model.StartLevel;
                part.Levels = model.StopLevel;
                part.Breadcrumb = model.Breadcrumb;
                part.AddHomePage = model.AddHomePage;
                part.AddCurrentPage = model.AddCurrentPage;
                part.ShowFullMenu = model.ShowFullMenu;
                part.MenuContentItemId = model.CurrentMenuId;
            }

            return Editor(part, shapeHelper);
        }

        protected override void Importing(MenuWidgetPart part, ImportContentContext context) {
            context.ImportAttribute(part.PartDefinition.Name, "StartLevel", x => part.StartLevel = Convert.ToInt32(x));
            context.ImportAttribute(part.PartDefinition.Name, "Levels", x => part.Levels = Convert.ToInt32(x));
            context.ImportAttribute(part.PartDefinition.Name, "Breadcrumb", x => part.Breadcrumb = Convert.ToBoolean(x));
            context.ImportAttribute(part.PartDefinition.Name, "AddCurrentPage", x => part.AddCurrentPage = Convert.ToBoolean(x));
            context.ImportAttribute(part.PartDefinition.Name, "AddHomePage", x => part.AddHomePage = Convert.ToBoolean(x));
            context.ImportAttribute(part.PartDefinition.Name, "ShowFullMenu", x => part.ShowFullMenu = Convert.ToBoolean(x));

            context.ImportAttribute(part.PartDefinition.Name, "Menu", x => part.MenuContentItemId = context.GetItemFromSession(x).Id);
        }

        protected override void Exporting(MenuWidgetPart part, ExportContentContext context) {
            var menuIdentity = _contentManager.GetItemMetadata(_contentManager.Get(part.MenuContentItemId)).Identity;
            context.Element(part.PartDefinition.Name).SetAttributeValue("Menu", menuIdentity);

            context.Element(part.PartDefinition.Name).SetAttributeValue("StartLevel", part.StartLevel);
            context.Element(part.PartDefinition.Name).SetAttributeValue("Levels", part.Levels);
            context.Element(part.PartDefinition.Name).SetAttributeValue("Breadcrumb", part.Breadcrumb);
            context.Element(part.PartDefinition.Name).SetAttributeValue("AddCurrentPage", part.AddCurrentPage);
            context.Element(part.PartDefinition.Name).SetAttributeValue("AddHomePage", part.AddHomePage);
            context.Element(part.PartDefinition.Name).SetAttributeValue("ShowFullMenu", part.ShowFullMenu);
        }


        // ADDED BY EMS - start
        private IEnumerable<MenuItem> Clone(IEnumerable<MenuItem> cachedMenuItems) {
            List<MenuItem> clone = new List<MenuItem>();

            if (cachedMenuItems != null) {
                foreach (var cachedMenuItem in cachedMenuItems) {
                    MenuItem menuItem = new MenuItem();

                    menuItem.Text = cachedMenuItem.Text;
                    menuItem.IdHint = cachedMenuItem.IdHint;
                    menuItem.Url = cachedMenuItem.Url;
                    menuItem.Href = cachedMenuItem.Href;
                    menuItem.Position = cachedMenuItem.Position;
                    menuItem.LinkToFirstChild = cachedMenuItem.LinkToFirstChild;
                    menuItem.LocalNav = cachedMenuItem.LocalNav;
                    menuItem.Culture = cachedMenuItem.Culture;
                    menuItem.Selected = cachedMenuItem.Selected;
                    menuItem.RouteValues = cachedMenuItem.RouteValues;
                    menuItem.Items = Clone(cachedMenuItem.Items);
                    menuItem.Permissions = Clone(cachedMenuItem.Permissions);
                    // In order to keep context, I am getting a new instance of the content item from the database / cache
                    string cacheKey = string.Format("MenuWidgetPartDriverContetCacheKey_{0}", cachedMenuItem.Content.Id);
                    menuItem.Content = GetCachedMenuPartContent(cachedMenuItem.Content.Id, cacheKey); //(IContent)_orchardServices.ContentManager.Get(cachedMenuItem.Content.Id).As<MenuPart>();
                    menuItem.Classes = cachedMenuItem.Classes;

                    clone.Add(menuItem);
                }
            }

            return clone;
        }

        private IEnumerable<Permission> Clone(IEnumerable<Permission> cachedMenuItemPermissions) {
            List<Permission> clone = new List<Permission>();

            if (cachedMenuItemPermissions != null) {
                foreach (var cachedMenuItemPermission in cachedMenuItemPermissions) {
                    Permission permission = new Permission();

                    permission.Name = cachedMenuItemPermission.Name;
                    permission.Description = cachedMenuItemPermission.Description;
                    permission.Category = cachedMenuItemPermission.Category;
                    permission.ImpliedBy = Clone(cachedMenuItemPermission.ImpliedBy);
                    permission.RequiresOwnership = cachedMenuItemPermission.RequiresOwnership;

                    clone.Add(permission);
                }
            }

            return clone;
        }

        private IEnumerable<MenuItem> GetCachedMenuItems(IContent menu, string cacheKey, bool isDeepClone = true) {
            List<MenuItem> cachedMenuItems = _cacheManager.Get(
                cacheKey,
                ctx => {
                    ctx.Monitor(_signals.When("NavigationContentItems.Changed"));
                    return _navigationManager.BuildMenu(menu).ToList();
                });

            //Make a new list (clone) so that we don't persist changes to this list back into the cached values
            if (isDeepClone) {
                List<MenuItem> menuItems = Clone(cachedMenuItems).ToList();
                return menuItems;
            }
            else {
                return cachedMenuItems;
            }
        }

        private IContent GetCachedMenuPartContent(int contentId, string cacheKey) {
            bool fromCache = true;

            IContent cachedMenuPartContent = _cacheManager.Get(
                cacheKey,
                ctx => {
                    ctx.Monitor(_signals.When("NavigationContentItems.Changed"));
                    fromCache = false;
                    return _orchardServices.ContentManager.Get(contentId).As<IContent>();
                });

            if (fromCache) {
                if (cachedMenuPartContent.ContentItem.ContentType == "HtmlMenuItem") {
                    // Due to the issues of HtmlMenuItem throwing MSPTC errors (i.e. the transaction scope seems to be closed and most hosts don't allow MSPTC)
                    //  on the following line - MenuItemLink-HtmlMenuItem.cshtml <span class="raw">@Html.Raw(Model.Content.BodyPart.Text)</span>
                    //  I am going to replace the cached version with a freshly queried version
                    //  This is not limited to HtmlMenuItem unfortunately, anytime in a view that low level data is attempted to be accessed this whole process can break
                    cachedMenuPartContent = (IContent)_orchardServices.ContentManager.Get(cachedMenuPartContent.ContentItem.Id).As<MenuPart>();
                }
            }

            return cachedMenuPartContent;
        }

        private IEnumerable<MenuItem> TryGetRouteValues(IEnumerable<MenuItem> menuItems) {
            foreach (var menuItem in menuItems) {
                if (menuItem.RouteValues == null) {
                    if (!String.IsNullOrEmpty(menuItem.Href)) {
                        menuItem.RouteValues = TryGetRouteValuesFromHref(menuItem.Href);
                    }
                }
                menuItem.Items = TryGetRouteValues(menuItem.Items);
            }
            return menuItems;
        }

        private RouteValueDictionary TryGetRouteValuesFromHref(string href) {
            RouteValueDictionary result = null;

            // Since GetRouteData (which is what Alias overrides) only accepts httpcontext, get context with passed in url
            //  The only way I can see this working is by using the current url context and see if it equals the href, should fix breadcrumb
            var httpContext = _workContextAccessor.GetContext().HttpContext;

            if (httpContext.Request.Path == href) {
                RouteData routeData = httpContext.Request.RequestContext.RouteData;
                if (routeData != null) {
                    result = new RouteValueDictionary(routeData.Values);
                }
            }

            return result;
        }
        // ADDED BY EMS - end
    }
}
